﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace theRift
{
    public class ThirdPersonCamera : Camera
    {
        protected Vector3 chasePosition;
        protected Vector3 velocity;

        protected Vector3 desiredPositionOffset = new Vector3(0, 2.0f, 2.0f);
        protected Vector3 desiredPosition;
        protected Vector3 lookAtOffset = new Vector3(0, 2.8f, 0);
        protected Vector3 lookAt;

        protected float stiffness = 1800.0f;
        protected float damping = 600.0f;
        protected float mass = 50.0f;

        public Vector3 ChasePosition
        {
            get { return chasePosition; }
            set { chasePosition = value; }
        }

        public Vector3 Velocity
        {
            get { return velocity; }
        }

        public Vector3 DesiredPositionOffset
        {
            get { return desiredPositionOffset; }
            set { desiredPositionOffset = value; }
        }

        public float DesiredPositionOffsetY
        {
            get { return desiredPositionOffset.Y; }
            set { desiredPositionOffset.Y = value; }
        }

        public Vector3 DesiredPosition
        {
            get
            {
                UpdateWorldPositions();

                return desiredPosition;
            }
        }

        public Vector3 LookAtOffset
        {
            get { return lookAtOffset; }
            set { lookAtOffset = value; }
        }

        public Vector3 LookAt
        {
            get
            {
                // Ensure correct value even if update has not been called this frame
                UpdateWorldPositions();

                return lookAt;
            }
        }

        public float Stiffness
        {
            get { return stiffness; }
            set { stiffness = value; }
        }

        public float Damping
        {
            get { return damping; }
            set { damping = value; }
        }

        public float Mass
        {
            get { return mass; }
            set { mass = value; }
        }

        public void Reset()
        {
            UpdateWorldPositions();

            // Stop motion
            velocity = Vector3.Zero;

            // Force desired position
            position = desiredPosition;

            UpdateView();
            UpdateProjection();
        }

        private void UpdateWorldPositions()
        {
            Matrix transform = Matrix.Identity;
            transform.Forward = direction;
            transform.Up = up;
            transform.Right = Vector3.Cross(up, direction);

            desiredPosition = chasePosition +
                Vector3.TransformNormal(desiredPositionOffset, transform);

            lookAt = chasePosition +
                Vector3.TransformNormal(lookAtOffset, transform);
        }

        // Update view matrix
        protected override void UpdateView()
        {
            view = Matrix.CreateLookAt(position,
                lookAt, up);

            needViewUpdate = false;
            needFrustumUpdate = true;
        }

        public override void Update(GameTime gameTime)
        {
            if (gameTime == null)
                throw new ArgumentNullException("gameTime");

            UpdateWorldPositions();

            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            // Calculate spring force
            Vector3 stretch = position - desiredPosition;
            Vector3 force = -stiffness * stretch - damping * velocity;

            // Apply acceleration
            Vector3 acceleration = force / mass;
            velocity += acceleration * elapsed;

            // Apply velocity
            position += velocity * elapsed;

            UpdateView();
            UpdateProjection();
        }
    }
}
